<?php

declare(strict_types=1);

namespace DreamCat\ControllerParamGen4Laravel\Dispatcher;

use BadMethodCallException;
use Dreamcat\Class2Array\JsonValueFixer;
use DreamCat\ControllerParamGen\ControllerParamGeneratorInterface;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Support\Responsable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Routing\Contracts\ControllerDispatcher;
use Illuminate\Routing\Route;
use Illuminate\Support\Stringable;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use ReflectionMethod;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;

/**
 * 控制器派发器
 * @author vijay
 */
class AnnotationCtlDispatcher implements ControllerDispatcher
{
    /**
     * @param ControllerParamGeneratorInterface $controllerParamGenerator 控制器参数生成器
     * @param Container $container 容器
     */
    public function __construct(
        private ControllerParamGeneratorInterface $controllerParamGenerator,
        private Container $container
    ) {
    }

    /**
     * @inheritDoc
     * @throws BindingResolutionException
     */
    public function dispatch(Route $route, $controller, $method): mixed
    {
        if (!method_exists($controller, $method)) {
            throw new BadMethodCallException("controller " . get_class($controller) . " has not method [{$method}]");
        }
        $parameters = $this->controllerParamGenerator->generateParams(
            new ReflectionMethod($controller, $method),
            $this->container->make(RequestInterface::class),
            $route->parameters()
        );

        if (method_exists($controller, 'callAction')) {
            $result = $controller->callAction($method, $parameters);
        } else {
            $result = $controller->{$method}(...array_values($parameters));
        }
        if (is_object($result)
            && !$result instanceof ResponseInterface
            && !$result instanceof Responsable
            && !$result instanceof Model
            && !$result instanceof Stringable
            && !$result instanceof SymfonyResponse
        ) {
            $result = $this->container->make(JsonValueFixer::class)->fixValue($result);
        }
        return $result;
    }

    /** @inheritDoc */
    public function getMiddleware($controller, $method): array
    {
        if (!method_exists($controller, 'getMiddleware')) {
            return [];
        }

        return collect($controller->getMiddleware())->reject(function ($data) use ($method) {
            return static::methodExcludedByOptions($method, $data['options']);
        })->pluck('middleware')->all();
    }

    /**
     * Determine if the given options exclude a particular method.
     *
     * @param string $method
     * @param array $options
     * @return bool
     */
    protected static function methodExcludedByOptions(string $method, array $options): bool
    {
        return (isset($options['only']) && !in_array($method, (array)$options['only'])) ||
            (!empty($options['except']) && in_array($method, (array)$options['except']));
    }
}

# end of file
